<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>canvas clip</title>
    <style>
        html,
        body {
            height: 100%;
            margin: 0;
        }
    </style>
</head>

<body>

    <style>
        body>div {
            position: relative;
            float: left;
        }

        img {
            display: block;
        }

        canvas {
            position: absolute;
            width: 100%;
            height: 100%;
            left: 0;
            top: 0;
            cursor: default;
        }
    </style>
    <div class="img-box" style="margin-left: 17px;">
        <img id="img" src="/1200x1200.jpg">
        <canvas id="c"></canvas>
    </div>
    <div>
        <button id="copy"> copy clip</button>
        <img src="" id="result">
    </div>

    <script>
        const WIDTH = 1200,
            HEIGHT = 1200;
        let pos = {},
            isMove = false,
            mouseDown = false,
            last = {},
            origin = null;

        c.width = WIDTH;
        c.height = HEIGHT;
        let ctx = c.getContext('2d');
        ctx.fillStyle = "rgba(0, 0, 0, 0.3)";

        function fillAll() {
            ctx.clearRect(0, 0, WIDTH, HEIGHT);
            ctx.fillRect(0, 0, WIDTH, HEIGHT);
            return true;
        }
        fillAll();
        resetPos();


        function mousedownHandler(event) {
            let cursor = c.style.cursor;
            let {
                offsetX: x,
                offsetY: y
            } = event, {
                x1,
                y1,
                x2,
                y2
            } = pos;

            switch (cursor) {
                case 'nesw-resize':
                    if (getTheClose(x, x1, x2) && getTheClose(y, y2, y1)) {
                        x1 = x2;
                    } else {
                        y1 = y2;
                    }
                    break;
                case 'nwse-resize':
                    if (getTheClose(x, x1, x2) && getTheClose(y, y1, y2)) {
                        x1 = x2;
                        y1 = y2;
                    }
                    break;
                case 'row-resize':
                    if (getTheClose(y, y1, y2)) {
                        y1 = y2;
                    }
                    break;
                case 'col-resize':
                    if (getTheClose(x, x1, x2)) {
                        x1 = x2;
                    }
                    break;
                case 'move':
                    isMove = true;
                    
                    origin = {
                        x,
                        y
                    };
                    last = {
                        x1: pos.x1,
                        x2: pos.x2,
                        y1: pos.y1,
                        y2: pos.y2
                    };

                    break;
                default:
                    x1 = x2 = x;
                    y1 = y2 = y;
                    fillAll();
                    setCursor(c, 'crosshair');
            }
            if (!isMove) {
                pos.x1 = x1;
                pos.x2 = x2;
                pos.y1 = y1;
                pos.y2 = y2;

            }
                mouseDown = true;
        }

        function mousemove(event) {
            let {
                offsetX: x,
                offsetY: y
            } = event;
            let {
                x1,
                y1,
                x2,
                y2
            } = pos;

            let r = 5,
                l = 3;
            if (mouseDown) {
                let cursor = c.style.cursor;
                switch (cursor) {
                    case 'crosshair':
                    case 'nesw-resize':
                    case 'nwse-resize':
                        x2 = x;
                        y2 = y;
                        break;
                    case 'row-resize':
                        y2 = y;
                        break;
                    case 'col-resize':
                        x2 = x;
                        break;
                    case 'move':
                        x1 = x1 + x - origin.x;
                        x2 = x2 + x - origin.x;
                        y1 = y1 + y - origin.y;
                        y2 = y2 + y - origin.y;
                        if(x1 < 0 || x2 < 0 || y1 <0 || y2 < 0 || x1 > WIDTH || x2 > WIDTH || y1 > HEIGHT || y2 > HEIGHT){
                            x1 = last.x1;
                            x2 = last.x2;
                            y1 = last.y1;
                            y2 = last.y2;
                        } else {
                            last.x1 = x1;
                            last.x2 = x2;
                            last.y1 = y1;
                            last.y2 = y2;
                            last = reverXY(last);
                        }
                        break;
                }
                clearRect(x1, y1, x2, y2);
            } else if ((isInArea(pos.y1, r)(y) && isInArea(pos.x1, r)(x)) ||
                (isInArea(pos.y2, r)(y) && isInArea(pos.x2, r)(x))) {
                setCursor(c, 'nwse-resize');
            } else if (
                (isInArea(pos.y1, r)(y) && isInArea(pos.x2, r)(x)) ||
                (isInArea(pos.y2, r)(y) && isInArea(pos.x1, r)(x))
            ) {
                setCursor(c, 'nesw-resize');
            } else if (isInRange([pos.x1, pos.x2], x) && (isInArea(pos.y1, l)(y) || isInArea(pos.y2, l)(
                    y))) {
                setCursor(c, 'row-resize');
            } else if (isInRange([pos.y1, pos.y2], y) && (isInArea(pos.x1, l)(x) || isInArea(pos.x2, l)(
                    x))) {
                setCursor(c, 'col-resize');
            } else if (isInRect(pos.x1, pos.y1, pos.x2, pos.y2)(x, y, r)) {
                setCursor(c, 'move');
            } else {
                setCursor(c, 'default');
            }
        }

        function mouseup(event) {
            let {
                offsetX: x,
                offsetY: y
            } = event;
            let cursor = c.style.cursor;
            switch (cursor) {
                case 'crosshair':
                case 'nesw-resize':
                case 'nwse-resize':
                    pos.x2 = x;
                    pos.y2 = y;
                    break;
                case 'row-resize':
                    pos.y2 = y;
                    break;
                case 'col-resize':
                    pos.x2 = x;
                    break;
                case 'move':
                    pos = {
                        x1: pos.x1 + x - origin.x,
                        x2: pos.x2 + x - origin.x,
                        y1: pos.y1 + y - origin.y,
                        y2: pos.y2 + y - origin.y
                    }
                    break;
            }
            mouseDown = false;
            isMove = false;
            setCursor(c, 'default');
            pos = reverXY();
        }
        c.addEventListener('mousedown', mousedownHandler);
        document.addEventListener('mousemove', throttle(mousemove, 50));
        document.addEventListener('mouseup', mouseup);

        function clearRect(x1, y1, x2, y2) {
            fillAll();
            // ctx.clearRect();
            ctx.clearRect(x1, y1, x2 - x1, y2 - y1);
            drawCorner(x1, y1, x2, y2);
        }

        function drawCorner(m1, n1, m2, n2, len = 10) {


            m1 = m1 > m2 ? [m2, m2 = m1][0] : m1;
            n1 = n1 > n2 ? [n2, n2 = n1][0] : n1;


            let {
                x1,
                y1,
                x2,
                y2
            } = limitMinAndMax(m1, m2, n1, n2);

            ctx.strokeStyle = "rgba(0, 0, 0, 1)";
            ctx.beginPath();
            ctx.moveTo(x1 + len, y1);
            ctx.lineTo(x1, y1);
            ctx.lineTo(x1, y1 + len);
            ctx.stroke();
            ctx.closePath();
            ctx.beginPath();
            ctx.moveTo(x2 - len, y1);
            ctx.lineTo(x2, y1);
            ctx.lineTo(x2, y1 + len);
            ctx.stroke();
            ctx.closePath();
            ctx.beginPath();
            ctx.moveTo(x1 + len, y2);
            ctx.lineTo(x1, y2);
            ctx.lineTo(x1, y2 - len);
            ctx.stroke();
            ctx.closePath();
            ctx.beginPath();
            ctx.moveTo(x2 - len, y2);
            ctx.lineTo(x2, y2);
            ctx.lineTo(x2, y2 - len);
            ctx.stroke();
            ctx.closePath();
        }

        function setCursor(target, style = "default") {
            (target || document).style.cursor = style;
        }

        function resetPos() {
            pos = {
                x1: 0,
                y1: 0,
                x2: 0,
                y2: 0
            }
        }
        /**
         * 如果x2 > x1;则反转
         */
        function reverXY(coordinate) {
            coordinate = coordinate || pos;
            let {
                x1,
                x2,
                y1,
                y2
            } = resort(coordinate);

            return limitMinAndMax(x1, x2, y1, y2);
        }

        function limitMinAndMax(x1, x2, y1, y2) {


            x1 = x1 > 0 ? x1 : 0;
            y1 = y1 > 0 ? y1 : 0;

            if (x2 > WIDTH) {
                x2 = WIDTH;
            }
            if (y2 > HEIGHT) {
                y2 = HEIGHT;
            }

            return {
                x1,
                y1,
                x2,
                y2
            }
        }

        function resort({
            x1,
            y1,
            x2,
            y2
        }) {
            x1 = x1 > x2 ? [x2, x2 = x1][0] : x1;
            y1 = y1 > y2 ? [y2, y2 = y1][0] : y1;

            return {
                x1,
                y1,
                x2,
                y2
            };
        }

        function isInRect(m1, n1, m2, n2) {
            let {
                x1,
                y1,
                x2,
                y2
            } = resort({
                x1: m1,
                y1: n1,
                x2: m2,
                y2: n2
            });

            return function (x, y, range = 5) {
                return x1 + 5 < x && x < x2 - 5 && y1 + 5 < y && y2 - 5 > y;
            }
        }

        function isInArea(start, range = 5) {
            let area = [start - range, start + range];
            return function (val) {
                return isInRange(area, val);
            }
        }

        function isInRange(range, val) {
            return (val > range[0] && val < range[1]);
        }

        function getTheClose(dot, x1, x2) {
            return Math.abs(dot - x1) < Math.abs(dot - x2);
        }
        let clipBtn = document.getElementById('copy');
        clipBtn.addEventListener('click', function () {
            let {
                x1,
                x2,
                y1,
                y2
            } = pos;
            x1 = x1 > x2 ? [x2, x2 = x1][0] : x1;
            y1 = y1 > y2 ? [y2, y2 = y1][0] : y1;
            let canvas = document.createElement('canvas');
            canvas.width = x2 - x1;
            canvas.height = y2 - y1;
            let ctx = canvas.getContext('2d');
            ctx.drawImage(img, x1, y1, x2 - x1, y2 - y1, 0, 0, x2 - x1, y2 - y1);
            result.src = canvas.toDataURL("image/png");
            fillAll();
        });

        function throttle(fn, time) {
            let prev = +new Date;
            return function (...args) {
                let ctx = this;
                if (((+new Date) - prev) > time) {
                    fn.apply(ctx, args);
                    prev = +new Date;
                }
            }
        }
    </script>
</body>

</html>
